#ifndef JOINGRAPH_H_
#define JOINGRAPH_H_

#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>

#include "graph_support/Graph.h"

#include "graph_support/utils/PtrAccessOpSimulator.h"


namespace GRAPH_SUPPORT_NS
{
namespace decorate
{

/**
 * expects AGraphT and BGraphT to have same vertices set and edge attributes type.
 * see JoinGraphTmpl::satisfiesRequirements
 */
template< typename AGraphT, typename BGraphT >
class JoinGraphTmpl : public Graph
{
private:
	typedef AGraphT AGraph;
	typedef BGraphT BGraph;
	typedef JoinGraphTmpl< AGraph, BGraph > MyT;

public:
	typedef typename AGraph::GrVertex GrVertex;
	typedef typename AGraph::EdgeAttributes EdgeAttributes;

	class GrEdge
	{
	public:
		GrEdge(
				bool isSwitched2A,
				const typename AGraph::GrEdgePtr & aedge,
				const typename BGraph::GrEdgePtr & bedge )
		// TODO: common style of switch-boolean-names
			: isSwitched2A( isSwitched2A )
			, aedge( aedge )
			, bedge( bedge )
		{
		}
		GrEdge()
		{
		}
	public:
		const GrVertex beg() const
		{
			return isSwitched2A ? aedge->beg() : bedge->beg();
		}
		const GrVertex end() const
		{
			return isSwitched2A ? aedge->end() : bedge->end();
		}
		size_t begId() const
		{
			return isSwitched2A ? aedge->begId() : bedge->begId();
		}
		size_t endId() const
		{
			return isSwitched2A ? aedge->endId() : bedge->endId();
		}
		EdgeAttributes & getAttributes()
		{
			return isSwitched2A ? aedge->getAttributes() : bedge->getAttributes();
		}
	private:
		bool isSwitched2A;
		typename AGraph::GrEdgePtr aedge;
		typename BGraph::GrEdgePtr bedge;
	};

	typedef utils::PtrAccessOpSimulator< GrEdge > GrEdgePtr;
	typedef typename AGraph::VertexIterator VertexIterator;

	class EdgeIterator : public Graph::EdgeIterator
	{
	public:
		typedef int difference_type;
		typedef GrEdgePtr value_type;
		typedef GrEdgePtr * pointer;
		typedef GrEdgePtr & reference;
		typedef std::forward_iterator_tag iterator_category;
	public:
		EdgeIterator(
				const typename AGraph::EdgeIterator & aiterBeg,
				const typename AGraph::EdgeIterator & aiterEnd,
				const typename BGraph::EdgeIterator & biter )
			: aiterCur( aiterBeg )
			, aiterEnd( aiterEnd )
			, afinished( false )
			, biter( biter )
		{
			if ( ! ( aiterCur != aiterEnd ) )
			{
				afinished = true;
			}
		}
	public:
		const EdgeIterator & operator ++ ()
		{
			if ( ! afinished )
			{
				assert( aiterCur != aiterEnd );
				++aiterCur;
				afinished = ! ( aiterCur != aiterEnd );
			} else
			{
				++biter;
			}
			return * this;
		}
		bool operator != ( const EdgeIterator & that ) const
		{
			return aiterCur != that.aiterCur || biter != that.biter;
		}
		GrEdgePtr operator * ()
		{
			if ( ! afinished )
			{
				return GrEdgePtr( aedge2edge( * * aiterCur ) );
			} else
			{
				return GrEdgePtr( bedge2edge( * * biter ) );
			}
		}
		GrEdgePtr * operator -> ()
		{
			return NULL;
		}
	private:
		typename AGraph::EdgeIterator aiterCur;
		typename AGraph::EdgeIterator aiterEnd;
		bool afinished;
		typename BGraph::EdgeIterator biter;
	};

public:
	typedef boost::shared_ptr< Graph > GraphPtr;

	/**
	 * write2a - switcher: which subgraph is for modification
	 */
	JoinGraphTmpl( AGraph & agr, BGraph & bgr, bool write2a = true )
		: agr( agr )
		, bgr( bgr )
		, write2a( write2a )
	{
		assert( satisfiesRequirements() );
	}

	JoinGraphTmpl( GraphPtr agrPtr, GraphPtr bgrPtr, bool write2a = true )
		: agrPtr( agrPtr )
		, bgrPtr( bgrPtr )
		, agr( * dynamic_cast< AGraph * >( agrPtr.get() ) )
		, bgr( * dynamic_cast< BGraph * >( bgrPtr.get() ) )
		, write2a( write2a )
	{
		assert( satisfiesRequirements() );
		assert( & agr != NULL );
		assert( & bgr != NULL );
	}

	size_t getVertexCount() const
	{
		return agr.getVertexCount();
	}

	VertexIterator getVerticesBeg()
	{
		return agr.getVerticesBeg();
	}

	VertexIterator getVerticesEnd()
	{
		return agr.getVerticesEnd();
	}

	EdgeIterator getEdgsBeg( const GrVertex & v )
	{
		return EdgeIterator(
				agr.getEdgsBeg( v ), agr.getEdgsEnd( v ),
				bgr.getEdgsBeg( v ) );
	}

	EdgeIterator getEdgsEnd( const GrVertex & v )
	{
		return EdgeIterator(
				agr.getEdgsEnd( v ), agr.getEdgsEnd( v ),
				bgr.getEdgsEnd( v ) );
	}

	template< typename FnT >
	void forEachOutEdge( GrVertex v, const FnT & fn )
	{
		forEachOutEdgeId( vert2num( v ), fn );
	}

	EdgeIterator getEdgsBegId( size_t vid )
	{
		return EdgeIterator(
				agr.getEdgsBegId( vid ), agr.getEdgsEndId( vid ),
				bgr.getEdgsBegId( vid ) );
	}

	EdgeIterator getEdgsEndId( size_t vid )
	{
		return EdgeIterator(
				agr.getEdgsEndId( vid ), agr.getEdgsEndId( vid ),
				bgr.getEdgsEndId( vid ) );
	}

	template< typename FnT >
	void forEachOutEdgeId( size_t vid, const FnT & fn )
	{
//		EdgeIterator stopeot = getEdgsEndId( vid );
//		for ( EdgeIterator eit = getEdgsBegId( vid ); eit != stopeot; ++eit )
//		{
//			fn( * * eit );
//		}
		agr.forEachOutEdgeId( vid, boost::bind( fn, boost::bind( & MyT::aedge2edge, _1 ) ) );
		bgr.forEachOutEdgeId( vid, boost::bind( fn, boost::bind( & MyT::bedge2edge, _1 ) ) );
	}

	size_t vert2num( const GrVertex & v ) const
	{
		return agr.vert2num( v );
	}

	GrVertex num2vert( size_t vid ) const
	{
		return agr.num2vert( vid );
	}

	void addEdge(
			const GrVertex & beg, const GrVertex & end,
			const EdgeAttributes & edgeAttrs = EdgeAttributes() )
	{
		if ( write2a )
		{
			agr.addEdge( beg, end, edgeAttrs );
		} else
		{
			bgr.addEdge( beg, end, edgeAttrs );
		}
	}

private:
	GraphPtr agrPtr;
	GraphPtr bgrPtr;
	AGraph & agr;
	BGraph & bgr;
	bool write2a;

private:
	bool satisfiesRequirements()
	{
		int n = agr.getVertexCount();
		if ( n != bgr.getVertexCount() )
		{
			return false;
		}
		for ( int i = 0; i < n; i++ )
		{
			typename AGraph::GrVertex av = agr.num2vert( i );
			typename BGraph::GrVertex bv = bgr.num2vert( i );
			if ( av != bv || ! ( av == bv ) )
			{
				return false;
			}
		}
		typename AGraph::EdgeAttributes * aeptr = NULL;
		typename BGraph::EdgeAttributes * beptr = NULL;
		if ( aeptr != beptr ) // A - EdgeAttributes and B - EdgeAttributes are the same
		{
			return false;
		}
		return true;
	}

	static GrEdge aedge2edge( typename AGraph::GrEdge & e )
	{
		return GrEdge( true, & e, NULL );
	}

	static GrEdge bedge2edge( typename BGraph::GrEdge & e )
	{
		return GrEdge( false, NULL, & e );
	}
};

}
}

#endif /* JOINGRAPH_H_ */
